<?php
/**
 * TinyCatPHP 极简、快速、创新、易用、高性能的PHP应用开发框架
 * User: CnTaiGe <hst042@163.com>
 * Date: 2017/11/8
 */

namespace TinyCatPHP\libs\db\drive;

use TinyCatPHP\exception\SysException;
use TinyCatPHP\libs\db\DBInterface;

class Mysql implements DBInterface
{
    protected $pdo;
    protected $table;
    protected $sql = ''; // sql语句
    protected $sql_value = [];//执行的值数组
    protected $_prefix;

    public function __construct($config)
    {
        if (!class_exists('\PDO'))
            throw new SysException('请配置 PDO 扩展');
        $this->pdo = new \PDO ("mysql:host=" . $config ['host'] . ';dbname=' . $config ['db_name'] . ';port=' . $config ['port'],
            $config ['user'],
            $config ['pwd'],
            array(
                \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
                \PDO::MYSQL_ATTR_INIT_COMMAND => 'set names ' . $config ['charset']
            ));
        $this->_prefix = $config['prefix'];
    }

    /**
     * 插入数据
     * @param string $table 动态更改表
     * @param array $data
     * @return mixed
     */
    public function insert($data, $table = '')
    {
        $_table = empty ($table) ? $this->table : $table;
        $this->sql = 'INSERT INTO ' . $_table . '(' . implode(',', array_map(function ($v) {
                return strpos($v, '.') === false ? '`' . $v . '`' : $v; // 处理‘.’连接符
            }, array_keys($data))) . ') VALUES(' . rtrim(str_repeat('?,', count($data)), ',') . ')';
        $this->sql_value = array_values($data);
        self::prepareValue($this->sql_value);
        return $this->pdo->lastInsertId();
    }

    /**
     * 预处理
     *
     * @param array $value
     *            $value
     *            占位符？ 对应的数组值
     * @return \PDOStatement
     */
    private function prepareValue($value)
    {
        $statement = $this->pdo->prepare($this->sql);
        $statement->execute($value);
        return $statement;
    }

    /**
     * 执行sql语句 删除、添加、更新
     * @param $sql
     * @param array $value 用于占位符？或 ':name'=>value
     * @return int 受影响的行数
     * 示例 execute ('insert into test (id,name) values(?,?)',[1,'test'])
     * 如不使用安全预处理 execute ('insert into test (id,name) values(1,"test")')
     */
    public function execute($sql, array $value = array())
    {
        $this->sql = $sql;
        $this->sql_value = $value;
        return self::prepareValue($value)->rowCount();
    }

    /**
     * 批量插入数据
     * @param $datas
     * @param string $table
     * @return mixed
     */
    public function insertMany($datas, $table = '')
    {
        $_table = empty ($table) ? $this->table : $table;
        $fields = [];
        foreach ($datas[0] as $k => $v) {
            $fields[] = $k;
        }
        $this->sql = "INSERT INTO {$_table} (" . implode(',', $fields) . ") VALUES ";
        $fieldValues = [];
        $fieldBinds = [];
        foreach ($datas as $key => $vk) {
            $bins = [];
            foreach ($vk as $fk => $fv) {
                $bins[] = ':' . $fk . $key;
                $fieldBinds[':' . $fk . $key] = $fv;
            }
            $fieldValues[] = '(' . implode(',', $bins) . ')';
        }
        $this->sql .= implode(',', $fieldValues);
        $this->sql_value = $fieldBinds;
        return self::prepareValue($fieldBinds)->rowCount();
    }

    /**
     * 查询数据
     *
     * @param string $sql
     *            完整的sql语句，安全预处理使用？代替参数
     * @param array $values
     *            替换占位符参数？的数据
     * @param callable $rowFn 按行获取回调函数
     * @return array 查询操作返回关联数组
     * 示例 query('select * from test id=? and name=?',[1,'test'])
     */
    public function query($sql, $values = [], callable $rowFn = null)
    {
        $this->sql_value = $values;
        $this->sql = $sql;
        $statement = !empty ($values) ? self::prepareValue($values) : $this->pdo->query($this->sql);
        $statement->setFetchMode(\PDO::FETCH_ASSOC);
        if ($rowFn !== null) {
            while ($row = $statement->fetch()) {
                $rowFn($row);
            }
        } else {
            $resultArr = $statement->fetchAll();
            return $resultArr;
        }
    }

    /**
     * 更新数据
     * @param string $where 查询条件 字符串 id=? and user=?
     * @param array $data 要修改的数据 ['pwd'=>'464646']
     * @param array $values 占用预处理？的值 [10,'user']
     * @param string $table
     * @return mixed
     */
    public function update($data, $where, $values = [], $table = '')
    {
        $_table = empty ($table) ? $this->table : $table;
        $fields = $this->getFieldsStr($data);
        $this->sql = "UPDATE {$_table} SET " . $fields . ' WHERE ' . $where;
        $this->sql_value = array_merge(array_values($data), $values);
        return $this->prepareValue($this->sql_value)->rowCount();
    }

    protected function getFieldsStr($vk)
    {
        $fields = implode(',', array_map(function ($v) {
            return strpos($v, '.') === false ? "`$v`=?" : $v . '=?';
        }, array_keys($vk)));
        return $fields;
    }

    /**
     * 删除数据
     * @param string $where 删除条件参数使用？代替
     * @param array $values 用于预处理替换where占位符？的值
     * @param string $table
     * @return mixed
     */
    public function delete($where, array $values = [], $table = '')
    {
        $_table = empty ($table) ? $this->table : $table;
        if (empty ($where))
            return 0;
        $this->sql = "DELETE FROM {$_table} WHERE " . $where;
        $this->sql_value = $values;
        $statement = self::prepareValue($values);
        return $statement->rowCount();
    }

    /**
     * 启动事务
     * @return bool
     */
    public function begintrans()
    {
        if ($this->pdo->inTransaction()) $this->pdo->commit();
        return $this->pdo->beginTransaction();
    }

    /**
     * 提交事务
     * @return bool
     */
    public function commit()
    {
        return $this->pdo->commit();
    }

    /**
     * 回滚事务
     * @return bool
     */
    public function rollback()
    {
        return $this->pdo->rollBack();
    }

    /**
     * 设置表
     * @param $table
     * @return DBInterface
     */
    public function setTable($table)
    {
        $this->table = $this->_prefix . $table;
        return $this;
    }

    /**
     * 获取最后查询的sql
     * @return mixed
     */
    public function getSql()
    {
        $sql = str_replace('?', "'%s'", $this->sql);
        $arr = $this->sql_value;
        array_unshift($arr, $sql);
        if (count($this->sql_value) != 0 && !isset($this->sql_value[0]))
            foreach ($this->sql_value as $key => $value) {
                $this->sql = str_replace($key, $value, $this->sql);
            } else
            $this->sql = call_user_func_array('sprintf', $arr);
        return $this->sql;
    }

    public function __call($name, $arguments)
    {
        $names = explode('_', $name);
        if (count($names) > 0 && $names[0] === 'find') {
            //处理快捷查询
            unset($names[0]);
            $sql = "SELECT * FROM {$this->table} WHERE ";
            $field = implode('_', $names);
            $orAnd = function ($oA) use ($field, $sql, $arguments) {
                if (strpos($field, $oA) !== false) {
                    $ors = explode($oA, $field);
                    if (count($ors) === 2) {
                        if (count($arguments) !== 2)
                            throw new SysException('查询参数个数不正确');
                        $oAStr = trim($oA, '_');
                        $sql .= "{$ors[0]}=? {$oAStr} {$ors[1]}=?";
                    }
                }
                return $sql;
            };
            if (count($names) === 1) {
                $sql .= "{$field}=?";
            } else {
                if (in_array('OR', $names))
                    $sql = $orAnd('_OR_');
                if (in_array('or', $names))
                    $sql = $orAnd('_or_');
                if (in_array('and', $names))
                    $sql = $orAnd('_and_');
                if (in_array('AND', $names))
                    $sql = $orAnd('_AND_');
            }
            if (substr_count($sql, '?') !== count($arguments))
                throw new SysException('查询参数个数不正确');
            $res = $this->query($sql . ' limit 1', $arguments);
            return isset($res[0]) ? $res[0] : [];
        }
        throw new  SysException('查不到对应的方法');
    }

    /**
     * @param string $where 查条件参数使用？代替
     * @param array $values 用于预处理替换where占位符？的值
     * @param string $table
     * @return int 返回的数量
     */
    public function count($where = '', $values = [], $table = '')
    {
        $_table = empty ($table) ? $this->table : $table;
        if (!empty($where))
            $where = "WHERE {$where}";
        $this->sql = "SELECT COUNT(*) AS c FROM {$_table} {$where} LIMIT 1";
        $rs = $this->query($this->sql, $values);
        return $rs[0]['c'];
    }
}